﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Framework
{
    class FrameCounter
    {
        protected int _frame;
        protected int _times;
        protected Action<float> _action;

        protected int _frameCount;
        protected int _timeCount;
        protected float _lastStartTime;
        
        public FrameCounter(int frame, int times, Action<float> action)
        {
            _frame = frame;
            _times = times;
            _action = action;
            _frameCount = 0;
            _timeCount = 0;
            _lastStartTime = Time.realtimeSinceStartup;
        }
        
        public bool Update()
        {
            ++_frameCount;
            if (_frameCount >= _frame)
            {
                float curTime = Time.realtimeSinceStartup;
                float deltaTime = curTime - _lastStartTime;
                _action.Invoke(deltaTime);
                _frameCount = 0;
                _lastStartTime = curTime;
                ++_timeCount;
                if (_times > 0 && _timeCount >= _times)
                {
                    return true;
                }
            }
            return false;
        }

        public bool Equals(Action<float> action)
        {
            return _action == action;
        }
    }

    struct TempFrameCount
    {
        public bool isAdd;
        public int frame;
        public int times;
        public Action<float> action;

        public TempFrameCount(int frame, Action<float> action, int times, bool isAdd)
        {
            this.frame = frame;
            this.times = times;
            this.action = action;
            this.isAdd = isAdd;
        }
    }
    
    public class ScheduleManager : MonoSingleton<ScheduleManager>
    {
        private List<FrameCounter> _listFrameCounter = new List<FrameCounter>();
        private List<TempFrameCount> _listTempFrameCount = new List<TempFrameCount>();
        
        private List<FrameCounter> _listLateFrameCounter = new List<FrameCounter>();
        private List<TempFrameCount> _listTempLateFrameCount = new List<TempFrameCount>();
        
        private bool _lockFrame = false;
        private bool _lockLateFrame = false;
        
        public void Update()
        {
            _lockFrame = true;
            int count = _listFrameCounter.Count;
            for (int i = count - 1; i >= 0; --i)
            {
                FrameCounter frameCounter = _listFrameCounter[i];
                if (frameCounter.Update())
                {
                    _listFrameCounter.RemoveAt(i);
                }
            }
            _lockFrame = false;
            count = _listTempFrameCount.Count;
            for (int i = 0; i < count; ++i)
            {
                TempFrameCount tempFrameCount = _listTempFrameCount[i];
                if (tempFrameCount.isAdd)
                {
                    AddFrameListener(tempFrameCount.frame, tempFrameCount.action, tempFrameCount.times);
                }
                else
                {
                    RemoveFrameListener(tempFrameCount.action);
                }
            }
            _listTempFrameCount.Clear();
        }

        public void LateUpdate()
        {
            _lockLateFrame = true;
            int count = _listLateFrameCounter.Count;
            for (int i = count - 1; i >= 0; --i)
            {
                FrameCounter frameCounter = _listLateFrameCounter[i];
                if (frameCounter.Update())
                {
                    _listLateFrameCounter.RemoveAt(i);
                }
            }
            _lockLateFrame = false;
            count = _listTempLateFrameCount.Count;
            for (int i = 0; i < count; ++i)
            {
                TempFrameCount tempFrameCount = _listTempLateFrameCount[i];
                if (tempFrameCount.isAdd)
                {
                    AddLateFrameListener(tempFrameCount.frame, tempFrameCount.action, tempFrameCount.times);
                }
                else
                {
                    RemoveLateFrameListener(tempFrameCount.action);
                }
            }
            _listTempLateFrameCount.Clear();
        }

        public void AddFrameListener(int frame, Action<float> action, int times = 0)
        {
            if (_lockFrame)
            {
                _listTempFrameCount.Add(new TempFrameCount(frame, action, times, true));
            }
            else
            {
                FrameCounter frameCounter = ContainsFrameCounter(action);
                if (frameCounter != null)
                {
                    RemoveFrameListener(action);
                }
                frameCounter = new FrameCounter(frame, times, action);
               _listFrameCounter.Add(frameCounter); 
            }
        }

        public void RemoveFrameListener(Action<float> action)
        {
            if (_lockFrame)
            {
                _listTempFrameCount.Add(new TempFrameCount(0, action, 0, false));
            }
            else
            {
                FrameCounter frameCounter = ContainsFrameCounter(action);
                if (frameCounter != null)
                {
                    _listFrameCounter.Remove(frameCounter);
                }
            }
        }

        private FrameCounter ContainsFrameCounter(Action<float> action)
        {
            for (int i = _listFrameCounter.Count - 1; i >= 0; --i)
            {
                FrameCounter frameCounter = _listFrameCounter[i];
                if (frameCounter.Equals(action))
                {
                    return frameCounter;
                }
            }
            return null;
        }
        
        public void AddLateFrameListener(int frame, Action<float> action, int times = 0)
        {
            if (_lockLateFrame)
            {
                _listTempLateFrameCount.Add(new TempFrameCount(frame, action, times, true));
            }
            else
            {
                FrameCounter frameCounter = ContainsLateFrameCounter(action);
                if (frameCounter != null)
                {
                    RemoveLateFrameListener(action);
                }
                frameCounter = new FrameCounter(frame, times, action);
                _listLateFrameCounter.Add(frameCounter); 
            }
        }

        public void RemoveLateFrameListener(Action<float> action)
        {
            if (_lockLateFrame)
            {
                _listTempLateFrameCount.Add(new TempFrameCount(0, action, 0, false));
            }
            else
            {
                FrameCounter frameCounter = ContainsFrameCounter(action);
                if (frameCounter != null)
                {
                    _listLateFrameCounter.Remove(frameCounter);
                }
            }
        }

        private FrameCounter ContainsLateFrameCounter(Action<float> action)
        {
            for (int i = _listLateFrameCounter.Count - 1; i >= 0; --i)
            {
                FrameCounter frameCounter = _listLateFrameCounter[i];
                if (frameCounter.Equals(action))
                {
                    return frameCounter;
                }
            }
            return null;
        }
    }
}